iT邦幫忙

2022 iThome 鐵人賽

DAY 28
0
Modern Web

Fastify 101系列 第 28

[Fastify] Day28 - Containerization (Dockerfile)

  • 分享至 

  • xImage
  •  

大家好,我是 Yubin

這邊文章將介紹把 Fastify App 包成 Image 的方法與注意事項。


前情提要

假設我們手上有一個 Fastify 的專案,為了部屬的準備,我想把這個 Fastify App 包成 Image,有了這個 Image 我就可以用 Docker, Podman, Kubernetes 等工具或平台進行部屬。

如果手上沒有 Fastify App 的朋友,可以參考 Fastify101: Hello World 建立一個基本的 Fastify 專案。或參考本篇文章最底下的完整專案連結。

以下皆使用 docker 指令為範例,如果使用 podman,只要把指令置換掉就好。
如:docker build -> podman build 等等。

Dockerfile

要建立 image,我們需要撰寫 Dockerfile。

Dockerfile 是一些指令的集合,用來定義這個 image 如何組成。

在專案根目錄建立一個 Dockerfile 的檔案:

FROM node:18.10.0

WORKDIR /app
COPY . /app

RUN npm install
RUN npm run build

CMD npm run start

FROM 定義 base image,也就是這個 image 要以哪個 image 為基底去生成。我這邊使用的是 node:18.10.0 這個 image,可以根據專案所用的版本或環境挑選適合的 tag。

WORKDIR 定義工作目錄,也就是設定 container 開始啟動之後,預設的工作目錄在哪裡。

COPY 把本機的檔案複製進 image 的生成環境中。這邊 COPY . /app 指的是把我的當前目錄的所有檔案及目錄 (連同子目錄) 複製進 image 裡面的 /app 目錄底下。

RUN 執行某個指令。

CMD 定義這個 image 啟動後,container 之後所執行的指令,如果這個指令執行結束或被關閉,container 也跟著被關閉。


docker build

Dockerfile 寫完,就可以把它 build 成 image。

docker build -t my-app:0.0.1 .

-t 為這個 image 定義 tag,這邊定義這個 image 叫做 my-app,tag 名稱是 0.0.1。

. 後面有一個點,是在說 Dockerfile 放在哪個目錄底下,因為我現在跟 Dockerfile 在同個目錄,所以寫一個點 (.)。

https://ithelp.ithome.com.tw/upload/images/20221013/20151148mjJbHbYRT7.jpg

成功執行完沒有錯誤訊息後,可以執行 docker images 看一下剛剛建立的 image。

https://ithelp.ithome.com.tw/upload/images/20221013/20151148oWBQcPCnKe.jpg

docker run

有了 image 之後,接這把這個 image 跑起來試試。

docker run -d -p 8888:8888 my-app:0.0.1

-d detach,將當於在背景執行。

-p 把我本機上的 8888 Port 跟 Container 裡面的 8888 Port 綁在一起。(因為我知道程式會聽在 8888 Port)

https://ithelp.ithome.com.tw/upload/images/20221013/20151148lfAWeEC8cd.jpg

執行完會顯示 Container 的 ID,可以利用 docker ps 來查看。

接著就可以打開瀏覽器或 Postman 打打看我們包出來的 Fastify App。

https://ithelp.ithome.com.tw/upload/images/20221013/20151148LvEzOeUHd7.jpg

至此我們就完成了 Fastify App 的容器化。

但是,一個小小 App 的 image 就佔了 1G 多的空間實在說不過去。

這邊我們可以換個適合的 base image。

Alpine

Alpine 是一個更為輕量化的 Linux 專案。

我們試著選用 alpine 版本的 node image 來包。

FROM node:18.10.0-alpine

WORKDIR /app
COPY . /app

RUN npm install
RUN npm run build

CMD npm run start

使用的是 node:18.10.0-alpine 的 image。

一樣執行 build 的指令。

docker build -t my-app:0.0.2 .

為了方便比較,把 tag 設定為 0.0.2

build 完,執行 docker images 來觀察。

https://ithelp.ithome.com.tw/upload/images/20221013/20151148VuKk6SWWWl.jpg

換個 base image 就可以有明顯的差距。

Multi Stage

我們上面的步驟中,使用 COPY . /app 把所有檔案都複製進 image 中,然後進行 npm installnpm run build 的動作,來將 TypeScript 的程式碼,編譯成 JavaScript 的執行檔。

但,我們的 src/ 還存在在裡面,但對我們部屬有用的只有編譯出來的 dist/ 目錄。

這邊我們可以透過 Dockerfile 的 Multi-stage 功能,build 完後只留下 build 出來的產物。

# Stage-1 for build
FROM node:18.10.0 as builder

WORKDIR /app
COPY . /app
RUN npm install
RUN npm run build

# Stage-2 for deployment
FROM node:18.10.0-alpine

WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package.json .
RUN npm install

CMD npm run start

可以看到,我們讓 Stage-1 當作我們用來 build 的工具,build 完之後,在 Stage-2 選用比較輕量的 image,再透過 COPY --from 來把 Stage-1 產出的執行檔複製過來。

如此一來,我們最後部屬出去的 image,裡面就不會記錄我們的 Source code。
而且用來 build 的那個 stage,也可以不太計較 image 的大小。因為許多情境下,在 build 階段會需要更多的相依套件。

docker build -t my-app:0.0.3 .

這邊把 tag 設為 0.0.3

https://ithelp.ithome.com.tw/upload/images/20221013/20151148Lca4a9c4oM.jpg


Production Dependencies

上面的 Dockerfile 中,我們都是使用 npm install 來安裝相依套件。

但我們在開發的時候,許多專案都是 only for development 使用的,像是 TypeScript 或 Prettier(排版工具) 等等。

就是安裝的時候加上 -D 安裝的那些套件。
npm i -D typescript

許多套件只是開發時期的工具,卻無助於我們程式在 Production 環境的運行。


使用 npm install 會根據 package.json 中定義的套件來進行安裝,有一些參數可以參考 npm 官方文件

其中,使用 --omit=dev 參數,可以讓我們不要安裝 devDependencies 區塊的套件。

npm install --omit=dev

我們可以使用這個參數,來進一步精簡最後要上 Production 的那個 Stage。

# Stage-1 for build
FROM node:18.10.0 as builder

WORKDIR /app
COPY . /app
RUN npm install
RUN npm run build

# Stage-2 for deployment
FROM node:18.10.0-alpine

WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package.json .
RUN npm install --omit=dev

CMD npm run start

再次執行 build 的指令。

docker build -t my-app:0.0.4 .

這邊把 tag 設為 0.0.4

https://ithelp.ithome.com.tw/upload/images/20221013/20151148lEfGidvXTS.jpg

可以看到少掉開發用的套件,我們最後產出的 image 變得更加輕量。

所以正確的定義套件是不是開發時期的相依套件很重要。


本文介紹了 Dockerfile 的撰寫,及一些讓 image 輕量的基本方法。

特別要提的是,要把 build 跟 deployment 分開,Dockerfile 的 Multi-stage 只是一種方法,更多人採用的方式可能是在 CI Pipeline 中定義負責 Build Source code 的階段跟 Build Image 的階段。概念上也是把 Stage 拆開,Dockerfile 只是一種可行的解,但不是唯一解。

要精簡 image 的大小還有許多方式跟細節,本文以 npm 的使用為主軸來跟各位分享。

在包 image 的時候,也要特別留意 .dockerignore 的正確定義,避免不小心把一些垃圾一起包進 image。

關於許多撰寫 Dockerfile 上的 Security 問題,可以參考這篇文章

在 Cloud Native 的世界,到處都是 Container,在有 Container 之前,image 的產生是非常重要的,大家要謹慎面對。

以上範例專案,可以參考 GitHub


上一篇
[Fastify] Day27 - 網站整合 Keycloak 登入 (fastify-keycloak-adapter)
下一篇
[Fastify] Day29 - Deployment
系列文
Fastify 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言